//===============================================================================
// Microsoft patterns & practices
// Guidance Explorer Offline Client
//===============================================================================
// Copyright  Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Net;
using System.Text;
using System.Web.Services.Protocols;
using System.Windows.Forms;
using System.Xml;
using GuidanceExplorer.Config;
using model = GuidanceExplorer.Model;
using online = GuidanceExplorer.OnlineGuidance;

namespace GuidanceExplorer.Services
{
    public class OnlineStorageService : IOnlineStorageService
    {
        enum UploadConfirmation { None, Yes, No }

        private static string GUIDFile = "OnlineGUID.txt";

        private SortedList<string, OnlineGuidance.OnlineStorage> OnlineStorageCollection;

        /// <summary>
        /// Whether the synchronization is happening or not.
        /// </summary>
        private static bool isSynchronizing = false;

        public bool IsSynchronizing
        {
            get
            {
                return isSynchronizing;
            }
        }

        public static OnlineStorageSection OnlineStorageConfigSection { private get; set; }
        //private static OnlineStorageSection OnlineStorageConfigSection
        //{
        //    get
        //    {
        //        Configuration configuration = ConfigurationManager.OpenExeConfiguration(Assembly.GetExecutingAssembly().Location);
        //        OnlineStorageSection section = configuration.GetSection("OnlineStorage") as OnlineStorageSection;
        //        return section;
        //    }
        //}
        #region IOnlineStorageService Members

        /// <summary>
        /// Return a list of available libraries in the online storage.
        /// </summary>
        /// <returns></returns>
        public model.LibraryCollection GetAvailableLibraries(AvailableLibrariesMode allowCreation)
        {
            if (!OnlineStorageConfigured())
            {
                return null;
            }

            model.LibraryCollection libCollection = model.LibraryCollection.New();
           
            //OnlineStorageSection section = ConfigurationManager.GetSection("OnlineStorage") as OnlineStorageSection;
            OnlineStorageSection section = OnlineStorageConfigSection;
            
            foreach (ServerElement server in section.Servers)
            {
                online.OnlineStorage storage = GetOnlineStorage(server.Url);
                if (storage == null)
                {
                    continue;
                }

                //Get the navigation tree
                online.NavigationTree navTree = storage.GetNavigationTree(1); //Only the first level is required

                //Convert online model to the offline model
                if (navTree.Libraries.Library != null)
                {
                    foreach (online.Library library in navTree.Libraries.Library)
                    {
                        //Create a new library.
                        model.Library newLibrary = model.Library.New();
                        newLibrary.WebSubscribed = true;
                        newLibrary.URL = storage.Url;
                        newLibrary.ReadProtection = (library.readProtection ? model.ReadProtection.ReadOnly : model.ReadProtection.ReadWrite);
                        newLibrary.Caption = library.caption;
                        newLibrary.Name = library.id.Split(':')[1];
                        newLibrary.SourceFileInfo = new FileInfo(Path.GetFullPath(Path.Combine(ModelStorageService.LibraryFolder, newLibrary.Name + ".xml")));

                        if (libCollection.ContainsKey(newLibrary.Name))
                        {
                            //in case two servers have the same library
                            continue;
                        }

                        //Process the views
                        if (library.Views != null)
                        {
                            foreach (online.View view in library.Views)
                            {
                                //Guidance type nodes are ignored.
                                if (view.id.Contains("guidanceType"))
                                {
                                    continue;
                                }

                                //Views within folders are ignored.
                                if (!string.IsNullOrEmpty(view.parentFolder))
                                {
                                    continue;
                                }

                                //Create the view.
                                model.View newView = CreateModelView(view);
                                newView.Container = newLibrary;

                                //Add the view to the library.
                                newLibrary.Views.Add(newView.Caption, newView);

                                //Get the contents of the view.
                                PopulateLocalView(storage, view, ref newView);
                            }
                        }

                        //Process the views
                        if (library.Folders != null)
                        {
                            foreach (online.Folder folder in library.Folders)
                            {
                                model.Folder newFolder = model.Folder.New();
                                newFolder.Caption = folder.caption;
                                newFolder.Container = newLibrary;
                                newFolder.ParentFolder = null;

                                ProcessFolderContents(newLibrary, folder, newFolder);

                                //Add the folder to the library
                                newLibrary.Folders.Add(newFolder.Caption, newFolder);
                            }
                        }
                        //Add the library to the guidance.
                        libCollection.Add(newLibrary.Name, newLibrary);
                    }
                }

                if (allowCreation == AvailableLibrariesMode.AllowCreation)
                {
                    //Add a placeholder for a new library creation.
                    model.Library newLibrary = model.Library.New();
                    newLibrary.WebSubscribed = true;
                    newLibrary.URL = storage.Url;
                    newLibrary.ReadProtection = model.ReadProtection.ReadWrite;
                    newLibrary.Caption = "<create a new library>";
                    newLibrary.Name = GuidanceExplorer.Commands.NewLibrary;
                    newLibrary.SourceFileInfo = new FileInfo(Path.GetFullPath(Path.Combine(ModelStorageService.LibraryFolder, newLibrary.Name + ".xml")));

                    //Add the library to the guidance.
                    libCollection.Add(newLibrary.Name, newLibrary);
                }
            }
            return libCollection;
        }

        private void ProcessFolderContents(model.Library library, online.Folder folder, model.Folder newFolder)
        {
            //Convert the child folders
            if (folder.Folders != null)
            {
                foreach (online.Folder childFolder in folder.Folders)
                {
                    model.Folder newChildFolder = model.Folder.New();
                    newChildFolder.Caption = childFolder.caption;
                    newChildFolder.Container = null;
                    newChildFolder.ParentFolder = newFolder;

                    newFolder.Folders.Add(newChildFolder.Caption, newChildFolder);

                    ProcessFolderContents(library, childFolder, newChildFolder);
                }
            }

            //Convert the child views
            if (folder.Views != null)
            {
                foreach (online.View childView in folder.Views)
                {
                    model.View newChildView = CreateModelView(childView);
                    newChildView.ParentFolder = newFolder;

                    newFolder.Views.Add(newChildView.Caption, newChildView);
                }
            }
        }

        private void PopulateLocalView(online.OnlineStorage storage, online.View view, ref model.View newView)
        {
            //check to see if this is a searchview
            if (!String.IsNullOrEmpty(view.criteria))
            {
                newView = CreateModelView(view);
            }
            else
            {
                //Get the view contents.
                online.ListView listView = storage.GetListViewForNodeId(view.id, string.Empty, null, 0, 0);
                if (listView.Content != null && listView.Content.GuidanceItem != null)
                {
                    //Create the guidance items for the view.
                    foreach (online.GuidanceItem item in listView.Content.GuidanceItem)
                    {
                        //If the item is already in the view, then ignore.
                        if (newView.Items.ContainsKey(item.id))
                        {
                            continue;
                        }

                        model.GuidanceItem guidanceItem = model.GuidanceItem.New();
                        guidanceItem.CSSFile = ModelStorageService.DefaultCSSFileName;
                        guidanceItem.DateTime = item.lastUpdate;
                        guidanceItem.GuidanceType = model.GuidanceType.New(item.guidanceTypeCaption);
                        guidanceItem.Id = item.id;
                        guidanceItem.Title = item.title;

                        newView.Items.Add(guidanceItem.Id, guidanceItem);
                    }
                }

                // Iterate all the items from the 
                model.GuidanceItemCollection itemsToDelete = model.GuidanceItemCollection.New();
                foreach (model.GuidanceItem item in newView.Items.Values)
                {
                    bool found = false;
                    if (listView.Content != null && listView.Content.GuidanceItem != null)
                    {
                        foreach (online.GuidanceItem onlineItem in listView.Content.GuidanceItem)
                        {
                            if (String.Equals(item.Id, onlineItem.id, StringComparison.InvariantCultureIgnoreCase))
                            {
                                found = true;
                                break;
                            }
                        }
                    }

                    // Check if the item is not in the local view
                    if (!found)
                    {
                        itemsToDelete.Add(item.Id, item);
                    }
                }

                //Delete the items that was not found locally
                foreach (model.GuidanceItem item in itemsToDelete.Values)
                {
                    newView.Items.Remove(item.Id);
                }
            }
        }


        /// <summary>
        /// Creates a view instance from the online view.
        /// </summary>
        /// <param name="library"></param>
        /// <param name="view"></param>
        /// <returns></returns>
        private model.View CreateModelView(online.View view)
        {
            model.SearchView newSearchView = null;
            model.View newView = null;

            if (!String.IsNullOrEmpty(view.criteria))
            {
                ModelStorageService storageService = new ModelStorageService();
                XmlReaderSettings settings = new XmlReaderSettings();
                settings.ConformanceLevel = ConformanceLevel.Fragment;
                StringReader criteriaStream = new StringReader(view.criteria);
                XmlReader reader = XmlReader.Create(criteriaStream, settings);

                newSearchView = model.SearchView.New();
                newSearchView.Id = view.id.Split(':')[1];
                newSearchView.Author = view.creator;
                newSearchView.Caption = view.caption;
                newSearchView.Criteria = storageService.ConvertXmlReaderToCriteria(reader);
                newSearchView.LastUpdate = view.lastUpdate;
            }
            else
            {
                newView = model.View.New();
                newView.Id = view.id.Split(':')[1];
                newView.Author = view.creator;
                newView.Caption = view.caption;
                newView.LastUpdate = view.lastUpdate;
            }

            if (newSearchView != null)
            {
                return newSearchView;
            }
            else
            {
                return newView;
            }
        }

        /// <summary>
        /// Creates a library instance from the online library.
        /// </summary>
        /// <returns></returns>
        private model.Library CreateModelLibrary(string url, online.Library onlineLibrary)
        {
            model.Library modelLibrary = model.Library.New();

            modelLibrary.Caption = onlineLibrary.caption;
            modelLibrary.Name = onlineLibrary.id.Substring(8);
            modelLibrary.URL = url;
            if (onlineLibrary.readProtection)
            {
                modelLibrary.ReadProtection = GuidanceExplorer.Model.ReadProtection.ReadOnly;
            }
            else
            {
                modelLibrary.ReadProtection = GuidanceExplorer.Model.ReadProtection.ReadWrite;
            }
            modelLibrary.WebSubscribed = true;
            modelLibrary.SourceFileInfo = new FileInfo(Path.GetFullPath(Path.Combine(ModelStorageService.LibraryFolder, modelLibrary.Caption + ".xml")));

            return modelLibrary;
        }

        /// <summary>
        /// Obtains the guidance items from the online storage to add each item to the library and creates 
        /// all the local files for the guidance library.
        /// </summary>
        /// <param name="modelStorage"></param>
        /// <param name="library"></param>
        public void AddGuidanceItemsFileReferences(Services.IModelStorageService modelStorage, model.Library library)
        {
            if (!OnlineStorageConfigured())
            {
                return;
            }

            online.OnlineStorage storage = GetOnlineStorage(library.URL);
            if (storage == null)
            {
                return;
            }

            //Process the items
            online.ListView listView = storage.GetListViewForNodeId(string.Format("library:{0}", library.Name), string.Empty, null, 0, 0);
            foreach (online.GuidanceItem guidanceItem in listView.Content.GuidanceItem)
            {
                //Create the new item
                model.GuidanceItemFileReference gifr = SetGuidanceItemFileReference(library, guidanceItem, model.GuidanceItemFileReference.New(), modelStorage);

                //Create the library folder
                if (!Directory.Exists(Path.GetDirectoryName(gifr.FullFileName)))
                {
                    Directory.CreateDirectory(Path.GetDirectoryName(gifr.FullFileName));
                }

                //Create a local file
                modelStorage.RenderGuidanceItemToFileInfo(gifr, new FileInfo(gifr.FullFileName), storage.GetGuidanceItemContent(guidanceItem.id));

                //Add the item to the library
                library.Items.Add(gifr.Id, gifr);
            }
        }

        /// <summary>
        /// Creates a GuidanceItemFileReference from a GuidanceItem online definition.
        /// </summary>
        /// <param name="library"></param>
        /// <param name="guidanceItem"></param>
        /// <returns></returns>
        public static model.GuidanceItemFileReference SetGuidanceItemFileReference(model.Library library, online.GuidanceItem guidanceItem, GuidanceExplorer.Model.GuidanceItemFileReference gifr, Services.IModelStorageService modelService)
        {
            if (!OnlineStorageConfigured())
            {
                return null;
            }

            string guidanceTypeName = string.Empty;
            model.GuidanceType itemGuidanceType = model.GuidanceType.Get(guidanceItem.guidanceTypeCaption);
            if (itemGuidanceType != null)
            {
                guidanceTypeName = itemGuidanceType.DisplayName;
            }

            gifr.Id = guidanceItem.id;
            gifr.Title = guidanceItem.title;
            gifr.Library = library;
            gifr.CSSFile = ModelStorageService.DefaultCSSFileName;
            string tempFileName = Path.Combine(guidanceTypeName, Path.ChangeExtension(gifr.Id, "xml"));
            tempFileName = Path.Combine("..\\Libraries\\" + library.Caption, tempFileName);
            gifr.FileName = tempFileName;
            gifr.DateTime = guidanceItem.lastUpdate;
            gifr.GuidanceType = model.GuidanceType.New(guidanceItem.guidanceTypeCaption);

            if (!String.IsNullOrEmpty(guidanceItem.images))
            {
                ModelStorageService storageService = new ModelStorageService();
                XmlReaderSettings settings = new XmlReaderSettings();
                settings.ConformanceLevel = ConformanceLevel.Fragment;
                StringReader imagesString = new StringReader(guidanceItem.images);
                XmlReader reader = XmlReader.Create(imagesString, settings);

                reader.Read();
                storageService.ConvertXmlReaderToImages(gifr, reader);
            }

            //Copy all the custom data
            if (guidanceItem.AnyAttr != null)
            {
                foreach (XmlAttribute attribute in guidanceItem.AnyAttr)
                {
                    if (gifr.CustomData.ContainsKey(attribute.Name))
                    {
                        gifr.CustomData[attribute.Name] = attribute.Value;
                    }
                    else
                    {
                        gifr.CustomData.Add(attribute.Name, attribute.Value);
                    }
                }
            }
            //Update the index with the metadata attributes
            modelService.UpdateIndex(gifr, false);

            return gifr;
        }

        
        public void CreateGuidanceType(string url, model.GuidanceType gtype, bool updateExistingType)
        {
            if (!OnlineStorageConfigured())
            {
                return;
            } 
            
            StreamReader reader;
            online.OnlineStorage storage = GetOnlineStorage(url);
            if (storage == null)
            {
                return;
            }

            try
            {
                online.GuidanceType onlineGType = new GuidanceExplorer.OnlineGuidance.GuidanceType();
                onlineGType.listCaption = gtype.ListDisplayName;
                onlineGType.newCaption = gtype.NewGuidanceTypeDisplayName;
                onlineGType.name = gtype.Name;
                onlineGType.caption = gtype.DisplayName;
                onlineGType.lastUpdate = gtype.DateTime;
                onlineGType.lastUpdateSpecified = true;
                onlineGType.id = gtype.Id;
                onlineGType.schemaId = gtype.SchemaId;

                if (File.Exists(gtype.ExampleFilePath))
                {
                    reader = new StreamReader(gtype.ExampleFilePath);
                    onlineGType.exampleContent = reader.ReadToEnd();
                }
                else
                {
                    onlineGType.exampleContent = "";
                }

                if (File.Exists(gtype.SchemaFilePath))
                {
                    reader = new StreamReader(gtype.SchemaFilePath);
                    onlineGType.templateSchemaContent = reader.ReadToEnd();
                }
                else
                {
                    onlineGType.templateSchemaContent = "";
                }

                if (File.Exists(gtype.TemplateFilePath))
                {
                    reader = new StreamReader(gtype.TemplateFilePath);
                    onlineGType.templateContent = reader.ReadToEnd();
                }
                else
                {
                    onlineGType.templateContent = "";
                }

                List<string> columns = new List<string>();
                foreach (model.Field f in gtype.Fields.Values)
                {
                    //non editable cols are default in every schema and don't need to be included here
                    if (f.Editable)
                    {
                        columns.Add(f.Name);
                    }
                }

                if (updateExistingType)
                {
                    storage.UpdateGuidanceType(onlineGType, columns.ToArray());
                }
                else
                {
                    onlineGType = storage.CreateGuidanceType(onlineGType, columns.ToArray());
                    gtype.SchemaId = onlineGType.schemaId;
                    gtype.Id = onlineGType.id;
                }
            }
            catch (SoapException ex)
            {
                if (ex.Message.IndexOf("Authorization") != -1)
                {
                   throw new Exception("You are not authorized to update the online store, please provide the correct credentials in the configuration file.");
                }

                throw;
            }
 
        }

        /// <summary>
        /// Publishes the guidance item to the online storage.
        /// </summary>
        /// <param name="guidanceItem"></param>
        public bool PublishGuidanceItem(model.Library library, model.GuidanceItem guidanceItem, string content, GuidanceItemOperation operation, bool noWarning)
        {
            if (!OnlineStorageConfigured())
            {
                return false;
            }

            online.OnlineStorage storage = GetOnlineStorage(library.URL);
            if (storage == null)
            {
                return false;
            }

            //Check if the item already exists
            online.GuidanceItem newItem = storage.GetGuidanceItemById(guidanceItem.Id);

            //Call the online storage.
            try
            {
                //Create the item for the online storage.
                newItem = CreateOnlineGuidanceItem(library, guidanceItem);
                if (newItem == null)
                {
                    return true;
                }

                if (operation == GuidanceItemOperation.Create)
                {
                    storage.CreateGuidanceItem(newItem, content);
                }
                else if (operation == GuidanceItemOperation.Update)
                {
                    storage.UpdateGuidanceItem(newItem, content);
                }
                else if (operation == GuidanceItemOperation.Delete)
                {
                    newItem.delete = true;
                    newItem.lastUpdate = DateTime.UtcNow;
                    storage.UpdateGuidanceItem(newItem, content);
                }

            }
            catch (SoapException ex)
            {
                if (ex.Code.Name == "StorageError")
                {
                    if (!noWarning)
                    {
                        MessageBox.Show(ex.Detail.InnerText);
                    }
                }
                else if (ex.Message.IndexOf("Authorization") != -1)
                {
                    MessageBox.Show("You are not authorized to update the online store, please provide the correct credentials in the configuration file.", Messages.GUIDANCE_EXPLORER, MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return false;
                }
                else if (ex.Message.IndexOf("cannot be null") != -1)
                {
                    //do nothing - this can happen if we are deleting a view that does not yet exist on the server
                }
                else
                {
                    MessageBox.Show(ex.Message);
                    return false;
                }
            }
            return true;
        }

        public online.GuidanceItem CreateOnlineGuidanceItem(model.Library library, model.GuidanceItem guidanceItem)
        {
            if (!OnlineStorageConfigured())
            {
                return null;
            }

            online.OnlineStorage storage = GetOnlineStorage(library.URL);
            if (storage == null)
            {
                return null;
            }

            online.GuidanceItem newItem = new online.GuidanceItem();
            newItem.creatorCaption = "guidancelibrary";
            newItem.guidanceTypeCaption = guidanceItem.GuidanceType.Name;
            newItem.id = guidanceItem.Id;
            newItem.lastUpdate = guidanceItem.DateTime;
            newItem.library = library.Name;
            newItem.title = guidanceItem.Title;

            if (guidanceItem.AllImages.Count > 0)
            {
                ModelStorageService storageService = new ModelStorageService();
                StringBuilder imagesString = new StringBuilder();
                XmlWriterSettings settings = new XmlWriterSettings();
                settings.ConformanceLevel = ConformanceLevel.Fragment;
                XmlWriter writer = XmlWriter.Create(imagesString, settings);

                storageService.RenderGuidanceItemImagesToXmlWriter(guidanceItem.AllImages, writer);
                writer.Flush();
                newItem.images = imagesString.ToString();
            }
            else
            {
                newItem.images = "";
            }

            //Get the guidance type.
            online.GuidanceType guidanceType = storage.GetGuidanceTypeByName(guidanceItem.GuidanceType.Name);
            if (guidanceType == null)
            {
                SyncronizeGuidanceTypes(storage);
                guidanceType = storage.GetGuidanceTypeByName(guidanceItem.GuidanceType.Id);
                if (guidanceType == null)
                {
                    return null;
                }
            }

            //Get the schema.
            online.Schema schema = storage.GetSchemaById(guidanceType.schemaId);
            if (schema == null)
            {
                throw new Exception("The schema for the guidance can't be found in the online storage.");
            }

            //Create the custom fields.
            List<XmlAttribute> attributes = new List<XmlAttribute>();
            foreach (online.Field field in schema.Fields)
            {
                if (guidanceItem.CustomData.ContainsKey(field.name))
                {
                    XmlAttribute attribute = new XmlDocument().CreateAttribute(field.name);
                    attribute.Value = guidanceItem[field.name];
                    attributes.Add(attribute);
                }
            }
            newItem.AnyAttr = attributes.ToArray();

            return newItem;
        }

        /// <summary>
        /// Delete the library from the model
        /// </summary>
        private void DeleteLibrary(model.Library library, model.Guidance guidance)
        {
            //Remove the library from the Guidance
            guidance.Libraries.Remove(library.Name);

            //Add the library to the deleted library collection so it will be removed when the model is saved.
           if (!guidance.DeletedLibraries.ContainsKey(library.Name))
            {
                guidance.DeletedLibraries.Add(library.Name, library);
            }
        }

        /// <summary>
        /// Checks to see if GE is configured to connect to a server
        /// </summary>
        /// <returns></returns>
        private static bool OnlineStorageConfigured()
        {
            //OnlineStorageSection section = ConfigurationManager.GetSection("OnlineStorage") as OnlineStorageSection;
            OnlineStorageSection section = OnlineStorageConfigSection;
            if (section.Servers.Count > 0)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Checks app.config for a guid for this install. If it doesn't exist, then create it
        /// </summary>
        private void LogUserGUID(online.OnlineStorage storage)
        {
            string GUID = global::GuidanceExplorer.Properties.Settings.Default.GuidanceExplorer_OnlineGuidance_GUID;
            if (string.IsNullOrEmpty(GUID))
            {
                GUID = Guid.NewGuid().ToString();
                global::GuidanceExplorer.Properties.Settings.Default.GuidanceExplorer_OnlineGuidance_GUID = Guid.NewGuid().ToString();
                global::GuidanceExplorer.Properties.Settings.Default.Save();
            }

            storage.LogUserGUID(GUID);
        }

        /// <summary>
        /// Performs a synchronization of the local storage and the online storage.
        /// </summary>
        /// <param name="modelService"></param>
        /// <param name="htmlRender"></param>
        public void Syncronize(Services.IModelStorageService modelService, IHtmlRenderService htmlRender, bool showMessages, DoWorkEventArgs eventArgs)
        {
            if (!OnlineStorageConfigured())
            {
                return;
            }

            if (isSynchronizing)
            {
                MessageBox.Show("Synchronization has already been started", "Online Store", MessageBoxButtons.OK, MessageBoxIcon.Information);
                if (eventArgs != null)
                {
                    eventArgs.Cancel = true;
                }
                return;
            }

            isSynchronizing = true;

            try
            {
                model.Guidance guidance = modelService.Guidance;

                int localAdded = 0;
                int onlineAdded = 0;
                int localDeleted = 0;
                int localUpdated = 0;
                int onlineUpdated = 0;
                int notChanged = 0;

                OnlineStorageSection section = OnlineStorageConfigSection;
                foreach (ServerElement server in section.Servers)
                {
                    online.OnlineStorage storage = GetOnlineStorage(server.Url);
                    if (storage == null)
                    {
                        continue;
                    }

                    SyncronizeLibraries(storage, guidance);
                }

                foreach (model.Library library in guidance.Libraries.Values)
                {
                    //Ignore the libraries that are not web subscribed.
                    if (!library.WebSubscribed || string.IsNullOrEmpty(library.URL))
                    {
                        continue;
                    }

                    online.OnlineStorage storage = GetOnlineStorage(library.URL);
                    if (storage == null)
                    {
                        continue;
                    }

                    LogUserGUID(storage);

                    SyncronizeGuidanceTypes(storage);

                    //Get the online library.
                    online.Library onlineLib = storage.GetLibraryById(library.Name);

                    //The library was not in the store.
                    if (onlineLib == null)
                    {
                        continue;
                    }

                    //Get library index.
                    online.LibraryIndex libIndex = null;
                    try
                    {
                        libIndex = storage.GetLibraryIndex(string.Format("library:{0}", library.Name));
                    }
                    catch
                    {
                        if (showMessages)
                        {
                            if (eventArgs != null)
                            {
                                eventArgs.Cancel = true;
                            }

                            MessageBox.Show("The synchronization can't connect to the online store.", Messages.ONLINE_STORE);
                            return;
                        }
                    }

                    //If there is no index, then skip the library.
                    if (libIndex == null)
                    {
                        continue;
                    }

                    library.OldReadProtection = library.ReadProtection;
                    try
                    {
                        library.ReadProtection = GuidanceExplorer.Model.ReadProtection.ReadOnly;

                        //Update the name of the library
                        library.Caption = onlineLib.caption;

                        //Syncronize the guidance items.
                        SynchronizeLibraryGuidanceItems(modelService, htmlRender, storage, library, libIndex, ref localAdded, ref onlineAdded, ref localDeleted, ref localUpdated, ref onlineUpdated, ref notChanged); //TODO: hmm automatic refactor :)

                        //Synchronize the views.
                        SynchronizeLibraryViews(modelService, htmlRender, storage, library, onlineLib);
                    }
                    catch (Exception e)
                    {
                        MessageBox.Show("Could not syncronize the online libraries. " + e.Message, "Guidance Explorer");
                    }
                    finally
                    {
                        library.ReadProtection = library.OldReadProtection;
                    }
                }
            }
            catch (Exception e) 
            { 
                MessageBox.Show("Could not syncronize the online libraries. " + e.Message, "Guidance Explorer");
            }
            finally
            {
                isSynchronizing = false;
            }
        }

        private void SyncronizeLibraries(online.OnlineStorage storage, model.Guidance guidance)
        {
            string[] libIdArray = storage.GetAllLibraryIds();
            foreach (string libraryId in libIdArray)
            {
               //check the online libraries to see if there are any new                
                bool found = false;
                model.Library foundLibrary = null;

                foreach (model.Library library in guidance.Libraries.Values)
                {
                    if (library.Name == libraryId)
                    {
                        foundLibrary = library;
                        found = true;
                        break;
                    }
                }

                online.Library onlineLib = storage.GetLibraryById(libraryId);
                if (onlineLib == null)
                {
                    continue;
                }
               
                if (!found)
                {
                    //add the online library to the local set
                    if (onlineLib.delete)
                    {
                        continue;
                    }
                    model.Library newLibrary = CreateModelLibrary(storage.Url, onlineLib);
                    guidance.Libraries.Add(newLibrary.Name, newLibrary);
                }
                else
                {
                    //if it exist locally check to see if we should delete it.
                    if (onlineLib.delete)
                    {
                        DeleteLibrary(foundLibrary, guidance);
                        continue;
                    }
                }
            }
        }

        private void CreateLocalGuidanceType(online.OnlineStorage storage, online.GuidanceType onlineType)
        {
            StreamWriter writer;
            model.GuidanceType modelType = model.GuidanceType.New(onlineType.name);

            modelType.Id = onlineType.id;
            modelType.ListDisplayName = onlineType.listCaption;
            modelType.NewGuidanceTypeDisplayName = onlineType.newCaption;
            modelType.Name = onlineType.name;
            modelType.DisplayName = onlineType.caption;
            modelType.ExampleFileName = onlineType.name + "_example.dat";
            modelType.TemplateFileName = onlineType.name + "_template.xml";
            modelType.DateTime = onlineType.lastUpdate;
            modelType.SchemaId = onlineType.schemaId;

            try
            {
                if (File.Exists(modelType.ExampleFilePath))
                {
                    File.Delete(modelType.ExampleFilePath);
                }
                writer = new StreamWriter(modelType.ExampleFilePath);
                writer.Write(onlineType.exampleContent);
                writer.Flush();

                if (File.Exists(modelType.TemplateFilePath))
                {
                    File.Delete(modelType.TemplateFilePath);
                }
                writer = new StreamWriter(modelType.TemplateFilePath);
                writer.Write(onlineType.templateContent);
                writer.Flush();

                if (File.Exists(modelType.SchemaFilePath))
                {
                    File.Delete(modelType.SchemaFilePath);
                }
                writer = new StreamWriter(modelType.SchemaFilePath);
                writer.Write(onlineType.templateSchemaContent);
                writer.Flush();
            }
            catch
            {
            }

            //Get the schema.
            online.Schema schema = storage.GetSchemaById(modelType.SchemaId);
            if (schema == null)
            {
                throw new Exception("The schema for the guidance can't be found in the online storage.");
            }

            int index = 0;
            foreach (model.Field modelField in model.GuidanceType.GetAllFields().Values)
            {
                //if its a non-editable field, always add it since these aren't tracked in the schema
                if (!modelField.Editable)
                {
                    Model.Field newField = modelField.Clone();
                    if (!modelType.Fields.ContainsKey(newField.Name))
                    {
                        modelType.Fields.Add(newField.Name, newField);
                    }
                    newField.Position = index++;
                }
            }

            foreach (online.Field onlineField in schema.Fields)
            {
                model.Field modelField;
                if (model.GuidanceType.GetAllFields().ContainsKey(onlineField.name))
                {
                    //if it is a default field, use the default values
                    modelField = model.GuidanceType.GetAllFields()[onlineField.name].Clone();
                    if (!modelType.Fields.ContainsKey(modelField.Name))
                    {
                        modelType.Fields.Add(modelField.Name, modelField);
                    }
                }
                else
                {
                    //if it is a custom field, use what's in the store.
                    modelField = model.Field.New(onlineField);
                    if (!modelType.Fields.ContainsKey(modelField.Name))
                    {
                        modelType.Fields.Add(modelField.Name, modelField);
                    }
                }
                modelField.Position = index++;
            }
        }

        private void SyncronizeGuidanceTypes(online.OnlineStorage storage)
        {
            online.GuidanceType[] gtypes = storage.GetGuidanceTypes();
            UploadConfirmation uploadConfirmation = UploadConfirmation.None;

            foreach (online.GuidanceType onlineGuidanceType in gtypes)
            {
                //if the online type doesn't exist locally, then add locally
                model.GuidanceType localGuidanceType = model.GuidanceType.GetById(onlineGuidanceType.id);
                if (localGuidanceType == null)
                {
                    CreateLocalGuidanceType(storage, onlineGuidanceType);
                }
                else
                {
                    if (CompareDateTime(onlineGuidanceType.lastUpdate, localGuidanceType.DateTime) > 0)
                    {
                        //treat the online type as the most recent
                        model.GuidanceType.RemoveGuidanceTypeById(onlineGuidanceType.id);
                        if (!onlineGuidanceType.delete)
                        {
                            CreateLocalGuidanceType(storage, onlineGuidanceType);
                        }
                    }
                    else if (CompareDateTime(onlineGuidanceType.lastUpdate, localGuidanceType.DateTime) < 0)
                    {
                        //treat the local type as the most recent
                        if (storage.AuthorizedToUpload && uploadConfirmation == UploadConfirmation.None)
                        {
                            if (MessageBox.Show(Messages.UPLOAD_GUIDANCETYPES_TO_ONLINE, Messages.ONLINE_STORE, MessageBoxButtons.YesNo) == DialogResult.Yes)
                            {
                                uploadConfirmation = UploadConfirmation.Yes;
                            }
                            else
                            {
                                uploadConfirmation = UploadConfirmation.No;
                            }
                        }

                        if (uploadConfirmation == UploadConfirmation.Yes)
                        {
                            CreateGuidanceType(storage.Url, model.GuidanceType.GetById(onlineGuidanceType.id), true);
                        }
                    }
                }
            }

            foreach (model.GuidanceType localGuidanceType in model.GuidanceType.AllGuidanceTypes)
            {
                bool found = false;

                foreach (online.GuidanceType onlineGuidanceType in gtypes)
                {
                    if (localGuidanceType.Id == onlineGuidanceType.id)
                    {
                        found = true;
                        break;
                    }
                }

                if (!found)
                {
                    if (storage.AuthorizedToUpload && uploadConfirmation == UploadConfirmation.None)
                    {
                        if (MessageBox.Show(Messages.UPLOAD_GUIDANCETYPES_TO_ONLINE, Messages.ONLINE_STORE, MessageBoxButtons.YesNo) == DialogResult.Yes)
                        {
                            uploadConfirmation = UploadConfirmation.Yes;
                        }
                        else
                        {
                            uploadConfirmation = UploadConfirmation.No;
                        }
                    }

                    if (uploadConfirmation == UploadConfirmation.Yes)
                    {
                        //publish to online store
                        CreateGuidanceType(storage.Url, localGuidanceType, false);
                    }
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="folder"></param>
        /// <returns></returns>
        private void ProcessChildFolders(model.Folder parentFolder, model.FolderCollection folders)
        {
            foreach (model.Folder folder in parentFolder.Folders.Values)
            {
                if (folder.Folders.Count == 0 && folder.Views.Count == 0)
                {
                    folders.Add(folder.Caption, folder);
                }
                ProcessChildFolders(folder, folders);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="library"></param>
        /// <param name="onlineView"></param>
        /// <param name="newView"></param>
        private bool SetViewFolder(model.Library library, online.View onlineView, model.View newView)
        {
            if (string.IsNullOrEmpty(onlineView.parentFolder))
            {
                newView.Container = library;
                //Add the view to the library.
                if (library.Views.ContainsKey(newView.Caption))
                {
                    MessageBox.Show("You have a local view that conflicts with a view in the online store. Please rename or remove the view: '" + library.Caption + "/" + newView.Caption + "'", Messages.GUIDANCE_EXPLORER, MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return false;
                }
                else
                {
                    library.Views.Add(newView.Caption, newView);
                }
            }
            else
            {
                bool root = true;
                string[] allFolders = onlineView.parentFolder.Split('/');
                model.Folder parentFolder = null;
                model.Folder currentFolder = null;
                foreach (string folderName in allFolders)
                {
                    currentFolder = null;
                    //Find the root fodler or the next nesting level.
                    if (root)
                    {
                        root = false;
                        if (library.Folders.ContainsKey(folderName))
                        {
                            currentFolder = parentFolder = library.Folders[folderName];
                        }
                    }
                    else
                    {
                        if (parentFolder.Folders.ContainsKey(folderName))
                        {
                            currentFolder = parentFolder.Folders[folderName];
                        }
                    }

                    //If the folder does not exist, then create it.
                    if (currentFolder == null)
                    {
                        currentFolder = model.Folder.New();
                        currentFolder.Caption = folderName;
                        if (parentFolder == null)
                        {
                            currentFolder.Container = library;
                            library.Folders.Add(currentFolder.Caption, currentFolder);
                        }
                        else
                        {
                            currentFolder.ParentFolder = parentFolder;
                            parentFolder.Folders.Add(currentFolder.Caption, currentFolder);
                        }
                    }

                    //Set the previous parent
                    parentFolder = currentFolder;
                }

                //Set the view to the folder
                if (currentFolder.Views.ContainsKey(newView.Caption))
                {
                    MessageBox.Show("You have a local view that conflicts with a view in the online store. Please rename or remove the view: '" + library.Caption + "/" + onlineView.parentFolder + "/" + newView.Caption + "'", Messages.GUIDANCE_EXPLORER, MessageBoxButtons.OK, MessageBoxIcon.Error);
                    return false;
                }
                else
                {
                    newView.ParentFolder = currentFolder;
                    currentFolder.Views.Add(newView.Caption, newView);
                }
            }
            return true;
        }

        /// <summary>
        /// Synchronize the views.
        /// </summary>
        /// <param name="modelService"></param>
        /// <param name="storage"></param>
        /// <param name="library"></param>
        private void SynchronizeLibraryViews(IModelStorageService modelService, IHtmlRenderService htmlRender, online.OnlineStorage storage, model.Library library, online.Library onlineLibrary)
        {
            //Find all the views in the remote that are not local.

            UploadConfirmation uploadConfirmation = UploadConfirmation.None;

            if (null != onlineLibrary.Views)
            {
                foreach (online.View onlineView in onlineLibrary.Views)
                {
                    model.View modelView = null;
                    bool found = false;
                    foreach (model.View tempView in library.AllViews.Values)
                    {
                        if (String.Equals(tempView.Id, onlineView.id.Split(':')[1], StringComparison.InvariantCultureIgnoreCase))
                        {
                            modelView = tempView;
                            found = true;
                            break;
                        }
                    }

                    if (!found)
                    {
                        if (onlineView.delete)
                        {
                            continue;
                        }

                        //Create the view.
                        model.View newView = CreateModelView(onlineView);

                        //Populate the local view.
                        PopulateLocalView(storage, onlineView, ref newView);

                        //set up folder structure
                        if (!SetViewFolder(library, onlineView, newView))
                        {
                            return;
                        }
                    }
                    else if (found)
                    {
                        if (CompareDateTime(onlineView.lastUpdate, modelView.LastUpdate) > 0)
                        {
                            //treat the online view as the most recent
                            if (onlineView.delete)
                            {
                                modelView.Remove(true);
                            }
                            else
                            {
                                if (modelView.Container != null)
                                {
                                    modelView.Container.Views.Remove(modelView.Caption);
                                    PopulateLocalView(storage, onlineView, ref modelView);
                                    SetViewFolder(library, onlineView, modelView);
                                }
                                else
                                {
                                    bool removeFolder = false;
                                    Model.Folder folder = null;
                                    modelView.ParentFolder.Views.Remove(modelView.Caption);
                                    
                                    if (modelView.ParentFolder.Views.Count == 0)
                                    {
                                        removeFolder = true;
                                        folder = modelView.ParentFolder;
                                    }

                                    PopulateLocalView(storage, onlineView, ref modelView);
                                    SetViewFolder(library, onlineView, modelView);
                                    
                                    if (folder != null && removeFolder)
                                    {
                                        folder.Remove();
                                    }
                                }

                                modelView.LastUpdate = onlineView.lastUpdate;
                                modelView.Caption = onlineView.caption;

                                //Synchronize view contents. Since the view was found on local and online store.
                                string test = modelView.Caption;
                            }
                        }
                        else if (CompareDateTime(onlineView.lastUpdate, modelView.LastUpdate) < 0)
                        {
                            //treat the local view as the most recent and update the online view.

                            if (storage.AuthorizedToUpload && uploadConfirmation == UploadConfirmation.None)
                            {
                                if (MessageBox.Show(Messages.UPLOAD_VIEWS_TO_ONLINE, Messages.ONLINE_STORE, MessageBoxButtons.YesNo) == DialogResult.Yes)
                                {
                                    uploadConfirmation = UploadConfirmation.Yes;
                                }
                                else
                                {
                                    uploadConfirmation = UploadConfirmation.No;
                                }
                            }

                            if (uploadConfirmation == UploadConfirmation.Yes)
                            {
                                CreateOnlineView(storage.Url, modelService, library, modelView, true);
                            }
                            
                        }
                        else if (CompareDateTime(onlineView.lastUpdate, modelView.LastUpdate) == 0)
                        {
                            //do nothing, they are the same
                        }

                    }
                }
            }

            //Find all the views in the local that are not remote.
            foreach (model.View modelView in library.AllViews.Values)
            {
                bool found = false;
                online.View onlineView = null;
                if (onlineLibrary.Views != null)
                {
                    foreach (online.View tempView in onlineLibrary.Views)
                    {
                        if (String.Equals(modelView.Id, tempView.id.Split(':')[1], StringComparison.InvariantCultureIgnoreCase))
                        {
                            onlineView = tempView;
                            found = true;
                            break;
                        }
                    }
                }

                if (!found)
                {
                    //A view has been added locally, publish it.
                    if (storage.AuthorizedToUpload && uploadConfirmation == UploadConfirmation.None)
                    {
                        if (MessageBox.Show(Messages.UPLOAD_VIEWS_TO_ONLINE, Messages.ONLINE_STORE, MessageBoxButtons.YesNo) == DialogResult.Yes)
                        {
                            uploadConfirmation = UploadConfirmation.Yes;
                        }
                        else
                        {
                            uploadConfirmation = UploadConfirmation.No;
                        }
                    }

                    if (uploadConfirmation == UploadConfirmation.Yes)
                    {
                        CreateOnlineView(storage.Url, modelService, library, modelView, false);
                    }
                }
            }
        }

        /// <summary>
        /// Provide a 'fuzzy' comparison of date time. If items times are within one minute of each other, then consider them the same
        /// </summary>
        /// <param name="date1"></param>
        /// <param name="date2"></param>
        /// <returns>
        /// Less than zero if date1 is less than date2
        /// 0 if date1 is the same as date2
        /// greater than zero if date1 is greater than date2
        /// </returns>
        private int CompareDateTime(DateTime date1, DateTime date2)
        {
            int compareResult = 0;
            TimeSpan fuzzyTime = TimeSpan.FromSeconds(10);

            if (date1 == date2)
            {
                compareResult = 0;
            }
            else if (date1 > date2)
            {
                if ((date1 - fuzzyTime) <= date2)
                {
                    compareResult = 0;
                }
                else
                {
                    compareResult = 1;
                }
            }
            else if (date1 < date2)
            {
                if ((date1 + fuzzyTime) >= date2)
                {
                    compareResult = 0;
                }
                else
                {
                    compareResult = -1;
                }
            }

            return compareResult;
        }

        /// <summary>
        /// Publish the guidance items for an entire library.
        /// </summary>
        /// <param name="modelService"></param>
        /// <param name="htmlRender"></param>
        /// <param name="storage"></param>
        /// <param name="library"></param>
        /// <param name="libIndex"></param>
        /// <param name="localAdded"></param>
        /// <param name="onlineAdded"></param>
        /// <param name="localDeleted"></param>
        /// <param name="localUpdated"></param>
        /// <param name="onlineUpdated"></param>
        /// <param name="notChanged"></param>
        private void SynchronizeLibraryGuidanceItems(Services.IModelStorageService modelService, IHtmlRenderService htmlRender, online.OnlineStorage storage, model.Library library, online.LibraryIndex libIndex, ref int localAdded, ref int onlineAdded, ref int localDeleted, ref int localUpdated, ref int onlineUpdated, ref int notChanged)
        {
            UploadConfirmation uploadConfirmation = UploadConfirmation.None;

            //Check each remote item date time with local last updated.
            foreach (online.GuidanceItemIndex itemIndex in libIndex.GuidanceItems)
            {
                model.GuidanceItemFileReference gifr = null;
                if (library.Items.ContainsKey(itemIndex.id))
                {
                    gifr = library.Items[itemIndex.id] as model.GuidanceItemFileReference;
                }
                if (gifr == null && !itemIndex.deleted)
                {
                    //There is a new item in the online library.

                    //Get the online guidance item definition
                    online.GuidanceItem onlineGuidanceItem = storage.GetGuidanceItemById(itemIndex.id);

                    //Create a local guidance item
                    gifr = SetGuidanceItemFileReference(library, onlineGuidanceItem, model.GuidanceItemFileReference.New(), modelService);

                    //Create the library folder
                    if (!Directory.Exists(Path.GetDirectoryName(gifr.FullFileName)))
                    {
                        Directory.CreateDirectory(Path.GetDirectoryName(gifr.FullFileName));
                    }
                    
                    //Create a local file
                    modelService.RenderGuidanceItemToFileInfo(gifr, new FileInfo(gifr.FullFileName), storage.GetGuidanceItemContent(onlineGuidanceItem.id));

                    //Add the item to the library.
                    library.Items.Add(gifr.Id, gifr);

                    //Update the count.
                    localAdded++;
                }
                else if (gifr != null)
                {
                    //The item already exist locally, compare date time.
                    if (itemIndex.deleted)
                    {
                        //Remove the guidance item from the model.
                        modelService.DeleteGuidanceItem(gifr, library);

                        //Remove guidance across the library
                        modelService.RemoveGuidanceItemFromLibrary(library, gifr);

                        //Remove the pointers from the user views.
                        foreach (model.View view in modelService.Guidance.UserViews.Views.Values)
                        {
                            model.SearchView searchView = view as model.SearchView;
                            //don't try to remove items from search views
                            if (searchView == null)
                            {
                                view.Items.Remove(gifr.Id);
                            }
                        }

                        //Remove the pointers from the global views.
                        foreach (model.View view in modelService.Guidance.GlobalViews.Views.Values)
                        {
                            model.SearchView searchView = view as model.SearchView;
                            //don't try to remove items from search views
                            if (searchView == null)
                            {
                                view.Items.Remove(gifr.Id);
                            }
                        }

                        //Remove guidance across 
                        modelService.RemoveGuidanceItemFromAllViews(modelService.Guidance.UserViews.Folders, gifr);
                        modelService.RemoveGuidanceItemFromAllViews(modelService.Guidance.GlobalViews.Folders, gifr);

                        //Update the count.
                        localDeleted++;
                    }
                    else if (CompareDateTime(gifr.DateTime,itemIndex.lastUpdate) == 0)
                    {
                        //Both items have the same date. 
                        //Check to make sure the item exists on the filesystem, if not then create it.
                        if (!File.Exists(gifr.FullFileName))
                        {
                            //Create a local file
                            modelService.RenderGuidanceItemToFileInfo(gifr, new FileInfo(gifr.FullFileName), storage.GetGuidanceItemContent(gifr.Id));
                        }
                        //Update the count.
                        notChanged++;
                    }
                    else if (CompareDateTime(gifr.DateTime,itemIndex.lastUpdate) < 0)
                    {
                        //Local item is older.

                        //Get the online guidance  item definition
                        online.GuidanceItem guidanceItem = storage.GetGuidanceItemById(itemIndex.id);

                        //Create a local file
                        gifr.DateTime = guidanceItem.lastUpdate;
                        modelService.RenderGuidanceItemToFileInfo(gifr, new FileInfo(gifr.FullFileName), storage.GetGuidanceItemContent(guidanceItem.id));

                        //Create a local guidance item
                        gifr = SetGuidanceItemFileReference(library, guidanceItem, gifr, modelService);

                        //Update the count.
                        localUpdated++;
                    }
                    else if (CompareDateTime(gifr.DateTime,itemIndex.lastUpdate) > 0)
                    {
                        //Online item is older.

                        if (storage.AuthorizedToUpload && uploadConfirmation == UploadConfirmation.None)
                        {
                            if (MessageBox.Show(Messages.UPLOAD_ITEMS_TO_ONLINE, Messages.ONLINE_STORE, MessageBoxButtons.YesNo) == DialogResult.Yes)
                            {
                                uploadConfirmation = UploadConfirmation.Yes;
                            }
                            else
                            {
                                uploadConfirmation = UploadConfirmation.No;
                            }
                        }

                        if (uploadConfirmation == UploadConfirmation.Yes)
                        {
                            using (StreamReader reader = new StreamReader(htmlRender.GetRawDocumentStream(gifr)))
                            {
                                if (!PublishGuidanceItem(library, gifr, reader.ReadToEnd(), GuidanceItemOperation.Update, false))
                                {
                                    break;
                                }
                            }

                            //Update the count.
                            onlineUpdated++;
                        }
                    }
                }
            }

            //Check if there is a new item in the local store.
            foreach (model.GuidanceItem guidanceItem in library.Items.Values)
            {
                bool found = false;
                foreach (online.GuidanceItemIndex itemIndex in libIndex.GuidanceItems)
                {
                    if (string.Equals(guidanceItem.Id, itemIndex.id, StringComparison.InvariantCultureIgnoreCase))
                    {
                        found = true;
                        break;
                    }
                }
                if (!found)
                {
                    //There is a new item in the local store

                    if (storage.AuthorizedToUpload && uploadConfirmation == UploadConfirmation.None)
                    {
                        if (MessageBox.Show(Messages.UPLOAD_ITEMS_TO_ONLINE, Messages.ONLINE_STORE, MessageBoxButtons.YesNo) == DialogResult.Yes)
                        {
                            uploadConfirmation = UploadConfirmation.Yes;
                        }
                        else
                        {
                            uploadConfirmation = UploadConfirmation.No;
                        }
                    }

                    if (uploadConfirmation == UploadConfirmation.Yes)
                    {
                        model.GuidanceItemFileReference gifr = guidanceItem as model.GuidanceItemFileReference;
                        if (gifr == null)
                        {
                            continue;
                        }

                        using (StreamReader reader = new StreamReader(htmlRender.GetRawDocumentStream(gifr)))
                        {
                            if (!PublishGuidanceItem(library, gifr, reader.ReadToEnd(), GuidanceItemOperation.Create, false))
                            {
                                break;
                            }
                        }
                    }

                    //Update the count.
                    onlineAdded++;
                }
            }
        }

        /// <summary>
        /// Creates a view in the online storage for the given library.
        /// </summary>
        /// <param name="subscribedLibrary"></param>
        /// <param name="view"></param>
        /// <param name="library"></param>
        /// <param name="modelService"></param>
        /// <param name="modelView"></param>
        /// <param name="updateExistingView"></param>
        /// <returns></returns>
        public string CreateOnlineView(string url, IModelStorageService modelService, model.Library library, model.View modelView, bool updateExistingView)
        {
            if (!OnlineStorageConfigured())
            {
                return string.Empty;
            }

            online.OnlineStorage storage = GetOnlineStorage(url);
            if (storage == null)
            {
                return string.Empty;
            }

            model.SearchView searchView = modelView as model.SearchView;
            bool isSearchView;

            try
            {
                StringBuilder sb = new StringBuilder();
                bool first = true;
                for (model.Folder tempNode = modelView.ParentFolder; tempNode != null; tempNode = tempNode.ParentFolder)
                {
                    if (first)
                    {
                        first = false;
                    }
                    else
                    {
                        sb.Insert(0, "/");
                    }
                    sb.Insert(0, tempNode.Caption);
                }
                string folderName = sb.ToString();

                online.View newView = new online.View();
                newView.caption = modelView.Caption;
                newView.creator = string.Empty;
                newView.creatorCaption = "guidancelibrary";
                newView.lastUpdate = modelView.LastUpdate;
                newView.lastUpdateSpecified = true;
                newView.delete = false;
                newView.id = modelView.Id;
                newView.library = library.Name;
                newView.parentFolder = folderName;

                if (searchView != null)
                {
                    ModelStorageService storageService = new ModelStorageService();
                    StringBuilder criteriaString = new StringBuilder();
                    XmlWriterSettings settings = new XmlWriterSettings();
                    settings.ConformanceLevel = ConformanceLevel.Fragment;
                    XmlWriter writer = XmlWriter.Create(criteriaString, settings);

                    storageService.RenderSearchViewCriteriaToXmlWriter(searchView.Criteria, writer);
                    writer.Flush();
                    newView.criteria = criteriaString.ToString();
                    isSearchView = true;
                }
                else
                {
                    newView.criteria = "";
                    isSearchView = false;
                }

                if (updateExistingView)
                {
                    storage.UpdateView(newView);
                }
                else
                {
                    storage.CreateView(newView);
                }

                if (!isSearchView)
                {
                    if (updateExistingView)
                    {
                        //remove existing items so they can be replaced below
                        storage.RemoveGuidanceItemsFromView(newView.id);
                    }

                    model.GuidanceItemFileReferenceCollection gifrs = model.GuidanceItemFileReferenceCollection.New();
                    foreach (model.GuidanceItem guidanceItem in modelView.Items)
                    {
                        model.GuidanceItemFileReference gifr = guidanceItem as model.GuidanceItemFileReference;

                        if (gifr == null)
                        {
                            if (guidanceItem.ReferencedItem == null)
                            {
                                modelService.Guidance.GetGuidanceItemFileReference(guidanceItem);
                            }
                            gifr = guidanceItem.ReferencedItem;
                        }

                        if (gifr.Library == modelView.GetContainerLibrary())
                        {
                            //only add items that are in the library
                            gifrs.Add(gifr.Id, gifr);
                        }
                    }
                    
                    if (gifrs.Count != 0)
                    {
                        AddGuidanceItemToView(url, new Guid(newView.id), gifrs);
                    }
                }

                return newView.id;
            }
            catch (SoapException ex)
            {
                if (ex.Message.IndexOf("Authorization") != -1)
                {
                   throw new Exception("You are not authorized to update the online store, please provide the correct credentials in the configuration file.");
                }
                throw;
            }
        }

        /// <summary>
        /// Add an item to the view.
        /// </summary>
        /// <param name="viewId"></param>
        /// <param name="gifr"></param>
        public void AddGuidanceItemToView(string url, Guid viewId, model.GuidanceItemFileReferenceCollection guidanceItems)
        {
            if (!OnlineStorageConfigured())
            {
                return;
            }

            online.OnlineStorage storage = GetOnlineStorage(url);
            if (storage == null)
            {
                return;
            }

            //Create the online representation of the guidance items.
            online.GuidanceItem[] newGuidanceItems = new online.GuidanceItem[guidanceItems.Count];
            int guidanceIdx = 0;
            foreach (model.GuidanceItemFileReference guidanceItem in guidanceItems)
            {
                newGuidanceItems[guidanceIdx] = new online.GuidanceItem();
                newGuidanceItems[guidanceIdx].id = guidanceItem.Id;
                guidanceIdx++;
            }

            try
            {
                //Add the items to the storage.
                storage.AddGuidanceItemsToView(viewId.ToString(), newGuidanceItems);
            }
            catch (SoapException ex)
            {
                if (ex.Code.Name == "StorageError")
                {
                }
                else
                {
                    throw;
                }
            }
        }

        /// <summary>
        /// Creates a library in the online storage.
        /// </summary>
        /// <param name="library"></param>
        public bool CreateLibrary(model.Library library, bool updateExisting)
        {
            if (!OnlineStorageConfigured())
            {
                MessageBox.Show("Guidance Explorer is not configured to connect to an online store, this operation cannot be completed", "Guidance Explorer");
                return false;
            }

            online.OnlineStorage storage = GetOnlineStorage(library.URL);
            if (storage == null)
            {
                return false;
            }

            online.Library newLibrary = new online.Library();
            newLibrary.id = library.Name;
            newLibrary.caption = library.Caption;
            newLibrary.delete = false;
            newLibrary.readProtection = library.ReadProtection == GuidanceExplorer.Model.ReadProtection.ReadOnly;

            try
            {
                if (updateExisting)
                {
                    if (!library.WebSubscribed)
                    {
                        return false;
                    }
                    storage.UpdateLibrary(newLibrary);
                }
                else
                {
                    storage.CreateLibrary(newLibrary);

                    library.Name = newLibrary.id;
                    library.WebSubscribed = true;
                }
            }
            catch (SoapException ex)
            {
                if (ex.Message.IndexOf("Authorization") != -1)
                {
                    MessageBox.Show("You are not authorized to update the online store, please provide the correct credentials in the configuration file.", Messages.GUIDANCE_EXPLORER, MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                else if (string.IsNullOrEmpty(ex.Detail.InnerText))
                {
                    throw new Exception(ex.Message);
                }
                else
                {
                    throw new Exception(ex.Detail.InnerText);
                }
                return false;
            }
            return true;
        }

        public string GetViewIdByCaption(model.Library library, string viewCaption)
        {
            if (!OnlineStorageConfigured())
            {
                return string.Empty;
            }

            online.OnlineStorage storage = GetOnlineStorage(library.URL);
            if (storage == null)
            {
                return string.Empty;
            }

            //Get the online library.
            online.Library onlineLib = storage.GetLibraryById(library.Name);

            if (onlineLib.Views != null)
            {
                foreach (online.View onlineView in onlineLib.Views)
                {
                    if (viewCaption == onlineView.caption)
                    {
                        return onlineView.id.Split(':')[1];
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// Removes a library from the online store
        /// </summary>
        /// <param name="library"></param>
        public void RemoveLibrary(model.Library library)
        {
            if (!OnlineStorageConfigured())
            {
                return;
            }

            if (!(MessageBox.Show(Messages.DELETE_ONLINE_LIBRARY, Messages.ONLINE_STORE, MessageBoxButtons.YesNo) == DialogResult.Yes))
            {
                return;
            }

            online.OnlineStorage storage = GetOnlineStorage(library.URL);
            if (storage == null)
            {
                return;
            }

            online.Library onlineLibrary = new online.Library();
            onlineLibrary.id = library.Name;
            onlineLibrary.delete = true;

            try
            {
                storage.UpdateLibrary(onlineLibrary);
            }
            catch (SoapException ex)
            {
                if (ex.Code.Name == "StorageError")
                {
                    MessageBox.Show(ex.Detail.InnerText);
                }
                else if (ex.Message.IndexOf("Authorization") != -1)
                {
                    MessageBox.Show("You are not authorized to update the online store, please provide the correct credentials in the configuration file.", Messages.GUIDANCE_EXPLORER, MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                else
                {
                    MessageBox.Show(ex.Message);
                }
            }
        }

        /// <summary>
        /// Remove a view in the online store based on the model definition.
        /// </summary>
        /// <param name="view"></param>
        public void RemoveView(model.View view)
        {
            if (!OnlineStorageConfigured())
            {
                return;
            }

            if (!(MessageBox.Show(Messages.DELETE_ONLINE_VIEW, Messages.ONLINE_STORE, MessageBoxButtons.YesNo) == DialogResult.Yes))
            {
                return;
            }

            view.LastUpdate = DateTime.UtcNow;

            model.Library library = view.GetContainerLibrary();
            if (library == null)
            {
                MessageBox.Show("The view is not a library view.");
                return;
            }

            online.OnlineStorage storage = GetOnlineStorage(library.URL);
            if (storage == null)
            {
                return;
            }

            string viewId = GetViewIdByCaption(library, view.Caption);

            online.View onlineView = new online.View();
            onlineView.id = viewId;
            onlineView.lastUpdate = view.LastUpdate;
            onlineView.delete = true;

            try
            {
                storage.UpdateView(onlineView);
            }
            catch (SoapException ex)
            {
                if (ex.Code.Name == "StorageError")
                {
                    MessageBox.Show(ex.Detail.InnerText);
                }
                else if (ex.Message.IndexOf("Authorization") != -1)
                {
                    MessageBox.Show("You are not authorized to update the online store, please provide the correct credentials in the configuration file.", Messages.GUIDANCE_EXPLORER, MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                else if (ex.Message.IndexOf("cannot be null") != -1)
                {
                    //do nothing - this can happen if we are deleting a view that does not yet exist on the server
                }
                else
                {
                    MessageBox.Show(ex.Message);
                }
            }
        }

        #endregion

        /// <summary>
        /// Creates a collection of online storage objects, one for each server in the app.config
        /// </summary>
        private void CreateOnlineStorageCollection()
        {
            OnlineStorageSection section = OnlineStorageConfigSection;
            OnlineStorageCollection = new SortedList<string, GuidanceExplorer.OnlineGuidance.OnlineStorage>(section.Servers.Count);
            GuidanceExplorer.OnlineGuidance.Authentication credentials;
            foreach (ServerElement server in section.Servers)
            {
                online.OnlineStorage storage = AddOnlineStorageCollection(server.Url);
                credentials = new GuidanceExplorer.OnlineGuidance.Authentication();
                credentials.User = server.UserName;
                credentials.Password = server.Password;
                storage.AuthenticationValue = credentials;
            }
        }

        /// <summary>
        /// add a new url to the online storage collection
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        private online.OnlineStorage AddOnlineStorageCollection(string url)
        {
            online.OnlineStorage storage = new online.OnlineStorage(url);
            string proxyUrl = global::GuidanceExplorer.Properties.Settings.Default.GuidanceExplorer_OnlineGuidance_OnlineStorage_WebProxy;
            if (!string.IsNullOrEmpty(proxyUrl))
            {
                WebProxy proxy = new WebProxy(proxyUrl);
                proxy.Credentials = CredentialCache.DefaultCredentials;
                storage.Proxy = proxy;
            }

            OnlineStorageCollection.Add(url, storage);

            return storage;
        }

        /// <summary>
        /// If configured, it sets the WebProxy settings
        /// </summary>
        /// <param name="storage"></param>
        private online.OnlineStorage GetOnlineStorage(string url)
        {
            if (String.IsNullOrEmpty(url))
            {
                return null;
            }

            online.OnlineStorage storage;

            if (OnlineStorageCollection == null)
            {
                CreateOnlineStorageCollection();
            }

            if (OnlineStorageCollection.ContainsKey(url))
            {
                storage = OnlineStorageCollection[url];
            }
            else
            {
                storage = AddOnlineStorageCollection(url);
            }

            return storage;
        }
    }
}
